﻿// HSS.Interlink.UploadFileDialog.cs
// ----------------------------------------------------------------------------
// Licensed under the MIT license (http://www.opensource.org/licenses/mit-license.html)
// ----------------------------------------------------------------------------
// HighSpeed-Solutions, LLC
// Copyright (c) 2001-2010
// ----------------------------------------------------------------------------
// File:       UploadFileDialog.cs
// Author:     HSS\gbanta
// Created:    07/16/2011
// Modified:   07/22/2011
// ----------------------------------------------------------------------------
namespace HSS.Interlink
{
	#region Using Directives
	using System;
	using System.Collections.Generic;
	using System.Collections.ObjectModel;
	using System.ComponentModel;
	using System.IO;
	using System.Linq;
	using System.Windows;
	using System.Windows.Controls;
	using System.Windows.Controls.Primitives;
	using System.Windows.Input;
	using System.Windows.Media;
	using System.Windows.Media.Imaging;
	#endregion

	#region UploadFileDialog
	/// <summary>
	/// UploadFileDialog presents a user with the options to select one or more
	/// files and upload them to the web server.
	/// </summary>
	public sealed class UploadFileDialog : ChildWindow
	{
		#region Events
		/// <summary>
		/// Event raised when the upload process has completed.
		/// </summary>
		public event EventHandler<UploadCompletedEventArgs> Completed;
		void OnCompleted(int completedCount)
		{
			var handler = this.Completed;
			if (null != handler)
				handler(this, new UploadCompletedEventArgs(completedCount));
		}
		/// <summary>
		/// Event raised when an individual file has been completely
		/// uploaded successfully.
		/// </summary>
		public event EventHandler<FileUploadCompletedEventArgs> FileUploaded;
		internal void OnFileUploaded(string fileName, string result)
		{
			if (this.Dispatcher.CheckAccess())
			{
				var handler = this.FileUploaded;
				if (null != handler)
					handler(this, new FileUploadCompletedEventArgs(fileName, result));
			}
			else
			{
				this.Dispatcher.BeginInvoke(() =>
				{
					OnFileUploaded(fileName, result);
				});
			}
		}
		#endregion

		#region Constants
		/// <summary>
		/// The default WebConfig ApplicationSetting Key (Default: 'UploadHandler') that contains the Handler for the file download.
		/// </summary>
		public const string DefaultHandlerKey = "UploadHandler";
		/// <summary>
		/// The default UploadUri (Default: '/FileUpload.ashx') that matches the Handler Path in the Web.Config
		/// </summary>
		public const string DefaultUploadUri = "/FileUpload.ashx";

		const double defaultFileNameWidth = 230;

		// CF == 207 - Focused
		// 3F == 63  - Unfocused
		const byte bkfocusAlpha = 207;
		const byte bkunfocusAlpha = 63;

		#endregion

		#region Fields

		Thumb E;
		Thumb W;
		Thumb N;
		Thumb S;
		Thumb SE;
		Thumb SW;
		Thumb NE;
		Thumb NW;

		bool closeOnCancel;

		UploadFileDialogViewModel viewModel;

		List<string> excludedExt = new List<string>();
		IEnumerable<FileInfo> preloadFiles;

		Grid root;
		Grid contentRoot;
		Grid contentGrid;
		DialogCloseButton closeButton;
		Border mainBackground;
		ActionButton btnBrowse;
		ActionButton btnUpload;
		ActionButton btnClearAll;
		ActionButton btnCancel;
		TextBlock lblResultsMessage;
		TextBlock txtFilesCount;
		TextBlock txbFileHeader;
		ListBox FileList;

		double minWidth;
		double newWidth = 0;

		double minHeight;
		double newHeight = 0;

		double originalLeft = 0;
		double newLeft = 0;

		double originalTop = 0;
		double newTop = 0;

		#endregion

		#region Constructors
		/// <summary>
		/// Constructor
		/// </summary>
		public UploadFileDialog()
			: this(DefaultGetMetadataCallback)
		{

		}
		/// <summary>
		/// Constructor
		/// </summary>
		/// <param name="getMetadataCallback">
		/// A delegate that will be called to retrieve additional metadata to 
		/// send along with the request to the server.
		/// </param>
		public UploadFileDialog(Func<UploadFileViewModel, string> getMetadataCallback)
			: this(getMetadataCallback, string.Empty, DefaultHandlerKey)
		{
		}
		/// <summary>
		/// Constructor
		/// </summary>
		/// <param name="getMetadataCallback">
		/// A delegate that will be called to retrieve additional metadata to 
		/// send along with the request to the server.
		/// </param>
		/// <param name="folderName">The name of the Folder the file(s) will be uploaded to. This is merely displayed in the header.</param>
		public UploadFileDialog(Func<UploadFileViewModel, string> getMetadataCallback, string folderName)
			: this(getMetadataCallback, folderName, DefaultHandlerKey)
		{
		}
		/// <summary>
		/// Constructor
		/// </summary>
		/// <param name="getMetadataCallback">
		/// A delegate that will be called to retrieve additional metadata to 
		/// send along with the request to the server.
		/// </param>
		/// <param name="folderName">The name of the Folder the file(s) will be uploaded to. This is merely displayed in the header.</param>
		/// <param name="handlerKey">The alternate WebConfig Key that contains the Handler for this file upload request</param>
		public UploadFileDialog(Func<UploadFileViewModel, string> getMetadataCallback, string folderName, string handlerKey)
		{
			this.DefaultStyleKey = typeof(UploadFileDialog);
			this.viewModel = new UploadFileDialogViewModel(this);
			if (null != getMetadataCallback)
				this.viewModel.GetMetadata = getMetadataCallback;
			if (!string.IsNullOrEmpty(folderName))
				this.FolderName = folderName;
			if (!string.IsNullOrEmpty(handlerKey))
				this.HandlerKey = handlerKey;
			this.viewModel.PropertyChanged += new PropertyChangedEventHandler(viewModel_PropertyChanged);
			this.DataContext = this.viewModel;
			this.Title = "HSS Interlink - File Upload";
			this.Loaded += (s, e) =>
				{
					if (null != preloadFiles)
					{
						if (!this.AddFiles(preloadFiles) && this.AutoClose)
							this.Close();
					}
				};
			this.KeyUp += (s, args) =>
				{
					if (args.Key == Key.Escape)
					{
						args.Handled = true;
						this.Close();
						return;
					}
					if (args.Key == Key.Z && Keyboard.Modifiers == (ModifierKeys.Control | ModifierKeys.Shift))
					{
						args.Handled = true;
						this.viewModel.IsDebugWriting = !this.viewModel.IsDebugWriting;
						if (this.viewModel.IsDebugWriting)
							this.FileList.Background = new SolidColorBrush(Color.FromArgb(64, 0, 0, 0));
						else
							this.FileList.Background = new SolidColorBrush(Colors.White);
					}
				};
			this.MouseRightButtonDown += (s, args) =>
			{
				args.Handled = true;
			};
			this.FileAlreadyExistsHandler = new Func<string, bool>((fileName) =>
				{
					if (this.AllowFileOverwrite && MessageBox.Show("The requested file [ " + fileName + " ] already exists; overwrite?", "File Exists", MessageBoxButton.OKCancel) == MessageBoxResult.OK)
						return true;
					return false;
				});
			this.ApplyTemplate();
		}
		#endregion

		#region Properties

		#region Background
		/// <summary>
		/// Gets or sets the Dialog Background
		/// </summary>
		public new SolidColorBrush Background
		{
			get
			{
				return (SolidColorBrush)base.GetValue(BackgroundProperty);
			}
			set
			{
				base.SetValue(BackgroundProperty, (DependencyObject)value);
				this.SetBackground(true);
			}
		}
		#endregion

		#region MaxConcurrentUploads
		/// <summary>
		/// Gets or sets the Maximum concurrent uploads. Default: 2
		/// </summary>
		public int MaxConcurrentUploads
		{
			get { return (int)GetValue(MaxConcurrentUploadsProperty); }
			set { SetValue(MaxConcurrentUploadsProperty, value); }
		}

		/// <summary>
		/// Using a DependencyProperty as the backing store for MaxConcurrentUploads.  This enables animation, styling, binding, etc...
		/// </summary>
		public static readonly DependencyProperty MaxConcurrentUploadsProperty =
			DependencyProperty.Register("MaxConcurrentUploads", typeof(int), typeof(UploadFileDialog), new PropertyMetadata(2));
		#endregion

		#region MaxFileSizeKB
		/// <summary>
		/// Gets or sets the Maximum size of a given file that can be uploaded.
		/// The default is Int32.MaxValue.
		/// </summary>
		public int MaxFileSizeKB
		{
			get { return (int)GetValue(MaxFileSizeKBProperty); }
			set { SetValue(MaxFileSizeKBProperty, value); }
		}
		/// <summary>
		/// Using a DependencyProperty as the backing store for MaxFileSizeKB.  This enables animation, styling, binding, etc...
		/// </summary>
		public static readonly DependencyProperty MaxFileSizeKBProperty =
			DependencyProperty.Register("MaxFileSizeKB", typeof(int), typeof(UploadFileDialog), new PropertyMetadata(int.MaxValue));
		#endregion

		#region ExcludeFileExtension
		/// <summary>
		/// Gets or sets the pipe delimited list of File extensions that are
		/// prohibited. Example: .exe|.mdb
		/// </summary>
		public string ExcludeFileExtension
		{
			get { return (string)GetValue(ExcludeFileExtensionProperty); }
			set { SetValue(ExcludeFileExtensionProperty, value); }
		}
		/// <summary>
		/// Using a DependencyProperty as the backing store for ExcludeFileExtension.  This enables animation, styling, binding, etc...
		/// </summary>
		public static readonly DependencyProperty ExcludeFileExtensionProperty =
			DependencyProperty.Register("ExcludeFileExtension", typeof(string), typeof(UploadFileDialog), new PropertyMetadata(".exe|.mdb|.mde|.accdb|.accde"));
		#endregion

		#region Filters
		/// <summary>
		/// Gets or sets the File filters for the Browse Dialog.
		/// </summary>
		public string Filters
		{
			get { return (string)GetValue(FiltersProperty); }
			set { SetValue(FiltersProperty, value); }
		}
		/// <summary>
		/// Using a DependencyProperty as the backing store for Filters.  This enables animation, styling, binding, etc...
		/// </summary>
		public static readonly DependencyProperty FiltersProperty =
			DependencyProperty.Register("Filters", typeof(string), typeof(UploadFileDialog), new PropertyMetadata("All Files (*.*)|*.*"));
		#endregion

		#region Comment
		/// <summary>
		/// Gets or sets the Comment to display to the user in the Header area.
		/// </summary>
		public string Comment
		{
			get { return (string)GetValue(CommentProperty); }
			set
			{
				if (string.IsNullOrEmpty(value))
					value = "The following files will be uploaded.";
				SetValue(CommentProperty, value);
			}
		}

		/// <summary>
		/// Using a DependencyProperty as the backing store for Comment.  This enables animation, styling, binding, etc...
		/// </summary>
		public static readonly DependencyProperty CommentProperty =
			DependencyProperty.Register("Comment", typeof(string), typeof(UploadFileDialog), new PropertyMetadata("The following files will be uploaded."));


		#endregion

		#region FolderName
		/// <summary>
		/// Gets or sets the name of the Folder and is displayed in the Header area.
		/// </summary>
		public string FolderName
		{
			get { return (string)GetValue(FolderNameProperty); }
			set
			{
				if (string.IsNullOrEmpty(value))
					value = "Upload Folder";
				SetValue(FolderNameProperty, value);
			}
		}
		/// <summary>
		/// Using a DependencyProperty as the backing store for FolderName.  This enables animation, styling, binding, etc...
		/// </summary>
		public static readonly DependencyProperty FolderNameProperty =
			DependencyProperty.Register("FolderName", typeof(string), typeof(UploadFileDialog), new PropertyMetadata("Upload Folder"));
		#endregion

		#region AllowNewUpload
		/// <summary>
		/// Gets or sets whether or not once the dialog is shown, if the User can initiate a new Upload.
		/// </summary>
		public bool AllowNewUpload
		{
			get { return _allowNewUpload; }
			set { _allowNewUpload = value; }
		} bool _allowNewUpload = true;
		#endregion

		#region AllowFileOverwrite
		/// <summary>
		/// Gets or sets whether or not your UploadHandler supports overwriting existing files.
		/// </summary>
		public bool AllowFileOverwrite
		{
			get { return (bool)GetValue(AllowFileOverwriteProperty); }
			set { SetValue(AllowFileOverwriteProperty, value); }
		}
		/// <summary>
		/// Using a DependencyProperty as the backing store for AllowFileOverwrite.  This enables animation, styling, binding, etc...
		/// </summary>
		public static readonly DependencyProperty AllowFileOverwriteProperty =
			DependencyProperty.Register("AllowFileOverwrite", typeof(bool), typeof(UploadFileDialog), new PropertyMetadata(false));
		/// <summary>
		/// Gets or sets a delegate that handles the File-Already-Exists response; returning true if
		/// OK to proceed or false to fail the upload. NOTE: Your UploadHandler must support 
		/// overwriting existing files.
		/// </summary>
		/// <remarks>
		/// <para>
		/// By default, the UploadFileDialog implements a basic Handler that if
		/// <see cref="AllowFileOverwrite"/> property is true and the user agrees
		/// will return true thereby allowing the upload to continue assuming the
		/// handler supports overwriting existing files.
		/// </para>
		/// </remarks>
		public Func<string, bool> FileAlreadyExistsHandler
		{
			get;
			set;
		}
		#endregion

		#region AutoUpload
		/// <summary>
		/// Gets or sets whether or not to begin uploading immediately after file selection (Default: false).
		/// </summary>
		/// <remarks><b>Default:</b> false</remarks>
		public bool AutoUpload
		{
			get;
			set;
		}
		#endregion

		#region AutoClose
		/// <summary>
		/// Gets or sets whether or not to automatically close this dialog when the Upload completes successfully.
		/// </summary>
		public bool AutoClose
		{
			get;
			set;
		}
		#endregion

		#region NoFileText
		/// <summary>
		/// Gets or sets the text to be displayed when no files have been selected or
		/// when the list is cleared.
		/// </summary>
		public string NoFilesText
		{
			get { return (string)GetValue(NoFilesTextProperty); }
			set
			{
				if (string.IsNullOrEmpty(value))
					value = Strings.UploadFileDialog_NoFilesText;
				SetValue(NoFilesTextProperty, value);
			}
		}
		/// <summary>
		/// Using a DependencyProperty as the backing store for NoFileText.  This enables animation, styling, binding, etc...
		/// </summary>
		public static readonly DependencyProperty NoFilesTextProperty =
			DependencyProperty.Register("NoFilesText", typeof(string), typeof(UploadFileDialog), new PropertyMetadata(Strings.UploadFileDialog_NoFilesText));
		#endregion

		#region AllowMultiselect
		/// <summary>
		/// Gets or sets whether or not the user can select multiple files (Default: True).
		/// </summary>
		public bool AllowMultiselect
		{
			get { return (bool)GetValue(AllowMultiselectProperty); }
			set { SetValue(AllowMultiselectProperty, value); }
		}
		/// <summary>
		/// Using a DependencyProperty as the backing store for AllowMultiselect.  This enables animation, styling, binding, etc...
		/// </summary>
		public static readonly DependencyProperty AllowMultiselectProperty =
			DependencyProperty.Register("AllowMultiselect", typeof(bool), typeof(UploadFileDialog), new PropertyMetadata(true));
		#endregion

		#region UploadUri
		/// <summary>
		/// Gets or sets the uri (absolute or relative) to the upload http handler. For OOB must be the absolute Uri. Default: '/FileUpload.ashx'.
		/// </summary>
		public string UploadUri
		{
			get
			{
				if (null == this.viewModel.UploadUri)
					return UploadFileDialog.DefaultUploadUri;
				return this.viewModel.UploadUri.ToString();
			}
			set
			{
				if (string.IsNullOrEmpty(value))
					throw new NullReferenceException("Cannot set the upload Uri to null or an empty string.");
				Uri uri = null;
				try
				{
					uri = FileQuery.ResolveUrl(value);
					this._uploadUri = value;
				}
				catch (Exception ex)
				{
					MessageBox.Show(ex.Message, "Upload", MessageBoxButton.OK);
					return;
				}
				this.viewModel.UploadUri = uri;
			}
		} string _uploadUri;
		#endregion

		#region State
		/// <summary>
		/// Gets the current state of the Upload.
		/// </summary>
		public UploadState State
		{
			get
			{
				if (null != this.viewModel)
					return this.viewModel.State;
				return UploadState.NotStarted;
			}
		}
		#endregion

		#region HandlerKey
		/// <summary>
		/// Gets the WebConfig ApplicationSetting Key that contains the Handler for the 
		/// file upload request.
		/// </summary>
		public string HandlerKey
		{
			get { return this.viewModel.HandlerKey; }
			private set { this.viewModel.HandlerKey = value; }
		}
		#endregion

		#region ViewFileDetail
		/// <summary>
		/// Gets or sets the delegate to call when the user clicks the View File Info button.
		/// </summary>
		public Func<UploadFileViewModel, ChildWindow> ViewFileDetail
		{
			get { return this.viewModel.ViewFileDetailCallback; }
			set { this.viewModel.ViewFileDetailCallback = value; }
		}
		#endregion

		#region TitleImage
		/// <summary>
		/// Gets the TitleImage.
		/// </summary>
		internal Image TitleImage
		{
			get
			{
				if (null == _titleImage)
					_titleImage = this.GetTemplateChild("TitleImage") as Image;
				return _titleImage;
			}
		} Image _titleImage;
		/// <summary>
		/// Gets or sets if the Title Image is visible.
		/// </summary>
		public bool IsTitleImageVisible
		{
			get { return _isTitleImageVisible; }
			set
			{
				_isTitleImageVisible = value;
				if (null != this.TitleImage)
				{
					if (!_isTitleImageVisible)
						this.TitleImage.Visibility = System.Windows.Visibility.Collapsed;
					else
						this.TitleImage.Visibility = System.Windows.Visibility.Visible;
				}
			}
		} bool _isTitleImageVisible = true;
		/// <summary>
		/// Gets the default Image Source for the TitleImage.
		/// </summary>
		public BitmapImage TitleImageSource
		{
			get { return (BitmapImage)GetValue(TitleImageSourceProperty); }
			set { SetValue(TitleImageSourceProperty, value); }
		}
		/// <summary>
		/// Using a DependencyProperty as the backing store for TitleImageSource.  This enables animation, styling, binding, etc...
		/// </summary>
		public static readonly DependencyProperty TitleImageSourceProperty =
			DependencyProperty.Register("TitleImageSource", typeof(BitmapImage), typeof(UploadFileDialog), new PropertyMetadata(ResourceMgr.InterlinkIcon));
		#endregion

		#region UploadedCount
		/// <summary>
		/// Gets the count of successfully uploaded files.
		/// </summary>
		public int UploadedCount { get; private set; }
		#endregion

		#region Files
		/// <summary>
		/// Gets the ReadOnlyCollection of the selected Files
		/// </summary>
		public ReadOnlyCollection<UploadFileViewModel> Files
		{
			get { return new ReadOnlyCollection<UploadFileViewModel>(this.viewModel.Files); }
		}
		#endregion

		/// <summary>
		/// Gets or sets the file (UploadFileViewModel) currently being uploaded.
		/// </summary>
		public UploadFileViewModel CurrentFile
		{
			get { return this.FileList.SelectedItem as UploadFileViewModel; }
			set { this.FileList.SelectedItem = value; }
		}

		#endregion

		#region Overrides
		/// <summary>
		/// Applies the template.
		/// </summary>
		public override void OnApplyTemplate()
		{
			this.UnsubscribeToTemplateEvents();

			base.OnApplyTemplate();

			this.root = this.GetTemplateChild("Root") as Grid;
			this.contentRoot = this.GetTemplateChild("ContentRoot") as Grid;
			this.txbFileHeader = this.GetTemplateChild("txbFileHeader") as TextBlock;
			this.contentGrid = this.GetTemplateChild("ContentGrid") as Grid;
			this.mainBackground = this.GetTemplateChild("MainBackground") as Border;
			this.closeButton = this.GetTemplateChild("CloseButton") as DialogCloseButton;
			this.FileList = this.GetTemplateChild("FileList") as ListBox;
			this.btnBrowse = this.GetTemplateChild("btnBrowse") as ActionButton;
			this.btnUpload = this.GetTemplateChild("btnUpload") as ActionButton;
			this.btnClearAll = this.GetTemplateChild("btnClearAll") as ActionButton;
			this.btnCancel = this.GetTemplateChild("btnCancel") as ActionButton;
			this.lblResultsMessage = this.GetTemplateChild("lblResultsMessage") as TextBlock;
			this.txtFilesCount = this.GetTemplateChild("txtFilesCount") as TextBlock;

			this.E = this.GetTemplateChild("E") as Thumb;
			this.S = this.GetTemplateChild("S") as Thumb;
			this.W = this.GetTemplateChild("W") as Thumb;
			this.N = this.GetTemplateChild("N") as Thumb;
			this.SE = this.GetTemplateChild("SE") as Thumb;
			this.SW = this.GetTemplateChild("SW") as Thumb;
			this.NE = this.GetTemplateChild("NE") as Thumb;
			this.NW = this.GetTemplateChild("NW") as Thumb;

			this.FormatTemplate();

			this.SubscribeToTemplateEvents();

			this.GotoState(this.viewModel.State.ToString(), false);
		}
		/// <summary>
		/// If autoUpload equals true, begins the upload process.
		/// </summary>
		protected override void OnOpened()
		{
			base.OnOpened();
			if (null == this.FileList)
				return;
			if (this.FileList.Items.Count > 0)
			{
				this.FileList.SelectedIndex = 0;
				if (this.AutoUpload && this.viewModel.ReadyCount > 0)
				{
					this.BeginUpload();
					return;
				}
				else
				{
					this.FileList.Focus();
					return;
				}
			}

			this.btnCancel.Focus();
			this.btnCancel.CaptureMouse();
		}
		/// <summary>
		/// Process the Closing process and optionally raises the Closing event.
		/// </summary>
		/// <param name="e">The Cancel event arguments.</param>
		protected override void OnClosing(CancelEventArgs e)
		{
			if (viewModel.State == UploadState.Completed)
			{
				if (!this.DialogResult.HasValue)
				{
					e.Cancel = true;
					this.DialogResult = true;
					return;
				}
				this.viewModel.Files.Clear();
				base.OnClosing(e);
			}
			else if (viewModel.State == UploadState.InProgress)
			{
				e.Cancel = true;
				if (MessageBox.Show(Strings.CancelUpload_Question, Strings.CancelUpload_Title, MessageBoxButton.OKCancel) == MessageBoxResult.OK)
				{
					this.closeOnCancel = true;
					this.Cancel();
				}
			}
			else
			{
				this.viewModel.Files.Clear();
				base.OnClosing(e);
			}
		}
		#endregion

		#region Methods

		/// <summary>
		/// Default GetMetadata Callback
		/// </summary>
		/// <param name="file"></param>
		/// <returns></returns>
		static string DefaultGetMetadataCallback(UploadFileViewModel file)
		{
			return string.Empty;
		}

		/// <summary>
		/// Opens a <see cref="UploadFileDialog"/> and returns without waiting for the <see cref="UploadFileDialog"/> to close.
		/// </summary>
		/// <param name="files">A collection of Files to upload.</param>
		public void Show(IEnumerable<FileInfo> files)
		{
			this.preloadFiles = files;
			this.Show();
		}
		/// <summary>
		/// Allows the user to browse for files first, and thens shows the <see cref="UploadFileDialog"/>.
		/// </summary>
		public void BrowseAndShow()
		{
			try
			{
				var dialog = new OpenFileDialog();
				dialog.Multiselect = this.AllowMultiselect;
				#region Apply the FileFilter
				try
				{
					if (!string.IsNullOrEmpty(this.Filters))
						dialog.Filter = this.Filters;
					else
						dialog.Filter = "All Files (*.*)|*.*";
				}
				catch
				{
					dialog.Filter = "All Files (*.*)|*.*";
				}
				#endregion

				if (dialog.ShowDialog() == true)
					this.Show(dialog.Files);
			}
			catch (Exception ex)
			{
				MessageBox.Show(ex.Message);
			}
		}
		/// <summary>
		/// Opens UploadFileDialog and returns without waiting for the UploadFileDialog to close.
		/// </summary>
		public new void Show()
		{
			base.Show();
		}

		void FormatTemplate()
		{
			if (null != this.root)
			{
				this.MaxHeight = double.PositiveInfinity;
				this.MaxWidth = double.PositiveInfinity;
			}

			if (null != this.contentRoot)
			{
				Size sz = new Size(Application.Current.Host.Content.ActualWidth, Application.Current.Host.Content.ActualHeight);
				double center = Math.Round((sz.Height - this.MinHeight) / 2);
				this.contentRoot.SetValue(Canvas.TopProperty, center);
				center = (sz.Width - this.MinWidth) / 2;
				this.contentRoot.SetValue(Canvas.LeftProperty, center);
			}

			if (null != this.contentGrid)
			{
				this.minWidth = this.contentGrid.MinWidth;
				this.minHeight = this.contentGrid.MinHeight;
			}

			if (null != this.FileList)
				this.FileList.AllowDrop = true;

			if (null != this.TitleImage)
			{
				if (!IsTitleImageVisible)
					this.TitleImage.Visibility = System.Windows.Visibility.Collapsed;
				else
					this.TitleImage.Visibility = System.Windows.Visibility.Visible;
			}

			this.SetBackground(true);
		}
		void SubscribeToTemplateEvents()
		{
			if (null != this.root)
				this.root.MouseRightButtonDown += new MouseButtonEventHandler(Root_MouseRightButtonDown);

			if (null != this.contentGrid)
				this.contentGrid.SizeChanged += contentGrid_SizeChanged;

			if (null != this.txtFilesCount)
				this.txtFilesCount.MouseRightButtonUp += new MouseButtonEventHandler(txtFilesCount_MouseRightButtonUp);

			if (null != this.FileList)
			{
				this.FileList.GotFocus += new RoutedEventHandler(FileList_GotFocus);
				this.FileList.Drop += new DragEventHandler(FileList_Drop);
				this.FileList.MouseRightButtonDown += new MouseButtonEventHandler(FileList_MouseRightButtonDown);
				this.FileList.MouseRightButtonUp += new MouseButtonEventHandler(FileList_MouseRightButtonUp);
			}

			if (null != this.btnBrowse)
				this.btnBrowse.Click += new RoutedEventHandler(btnBrowse_Click);

			if (null != this.btnUpload)
				this.btnUpload.Click += new RoutedEventHandler(btnUpload_Click);

			if (null != this.btnClearAll)
				this.btnClearAll.Click += new RoutedEventHandler(btnClearAll_Click);

			if (null != this.btnCancel)
				this.btnCancel.Click += new RoutedEventHandler(btnCancel_Click);

			if (null != this.E)
			{
				this.E.DragStarted += thbRight_DragStarted;
				this.E.DragDelta += thbRight_DragDelta;
			}

			if (null != this.S)
			{
				this.S.DragStarted += thbBottom_DragStarted;
				this.S.DragDelta += thbBottom_DragDelta;
			}

			if (null != this.W)
			{
				this.W.DragStarted += thbLeft_DragStarted;
				this.W.DragDelta += thbLeft_DragDelta;
			}

			if (null != this.N)
			{
				this.N.DragStarted += thbTop_DragStarted;
				this.N.DragDelta += thbTop_DragDelta;
			}

			if (null != this.SE)
			{
				this.SE.DragStarted += thbBottomRight_DragStarted;
				this.SE.DragDelta += thbBottomRight_DragDelta;
			}

			if (null != this.SW)
			{
				this.SW.DragStarted += thbBottomLeft_DragStarted;
				this.SW.DragDelta += thbBottomLeft_DragDelta;
			}

			if (null != this.NE)
			{
				this.NE.DragStarted += thbTopRight_DragStarted;
				this.NE.DragDelta += thbTopRight_DragDelta;
			}

			if (null != this.NW)
			{
				this.NW.DragStarted += thbTopLeft_DragStarted;
				this.NW.DragDelta += thbTopLeft_DragDelta;
			}

			this.GotFocus += new RoutedEventHandler(UploadFileDialog_GotFocus);
			this.LostFocus += new RoutedEventHandler(UploadFileDialog_LostFocus);
		}
		void UnsubscribeToTemplateEvents()
		{
			this.GotFocus -= new RoutedEventHandler(UploadFileDialog_GotFocus);

			this.LostFocus -= new RoutedEventHandler(UploadFileDialog_LostFocus);

			if (null != this.root)
				this.root.MouseRightButtonDown -= new MouseButtonEventHandler(Root_MouseRightButtonDown);

			if (null != this.contentGrid)
				this.contentGrid.SizeChanged -= contentGrid_SizeChanged;

			if (null != this.txtFilesCount)
				this.txtFilesCount.MouseRightButtonUp -= new MouseButtonEventHandler(txtFilesCount_MouseRightButtonUp);

			if (null != this.FileList)
			{
				this.FileList.GotFocus -= new RoutedEventHandler(FileList_GotFocus);
				this.FileList.Drop -= new DragEventHandler(FileList_Drop);
				this.FileList.MouseRightButtonDown -= new MouseButtonEventHandler(FileList_MouseRightButtonDown);
				this.FileList.MouseRightButtonUp -= new MouseButtonEventHandler(FileList_MouseRightButtonUp);
			}

			if (null != this.btnBrowse)
				this.btnBrowse.Click -= new RoutedEventHandler(btnBrowse_Click);

			if (null != this.btnUpload)
				this.btnUpload.Click -= new RoutedEventHandler(btnUpload_Click);

			if (null != this.btnClearAll)
				this.btnClearAll.Click -= new RoutedEventHandler(btnClearAll_Click);

			if (null != this.btnCancel)
				this.btnCancel.Click -= new RoutedEventHandler(btnCancel_Click);

			if (null != this.txtFilesCount)
				this.txtFilesCount.MouseRightButtonUp -= new MouseButtonEventHandler(txtFilesCount_MouseRightButtonUp);


			if (null != this.E)
			{
				this.E.DragStarted -= thbRight_DragStarted;
				this.E.DragDelta -= thbRight_DragDelta;
			}

			if (null != this.S)
			{
				this.S.DragStarted -= thbBottom_DragStarted;
				this.S.DragDelta -= thbBottom_DragDelta;
			}

			if (null != this.W)
			{
				this.W.DragStarted -= thbLeft_DragStarted;
				this.W.DragDelta -= thbLeft_DragDelta;
			}

			if (null != this.N)
			{
				this.N.DragStarted -= thbTop_DragStarted;
				this.N.DragDelta -= thbTop_DragDelta;
			}

			if (null != this.SE)
			{
				this.SE.DragStarted -= thbBottomRight_DragStarted;
				this.SE.DragDelta -= thbBottomRight_DragDelta;
			}

			if (null != this.SW)
			{
				this.SW.DragStarted -= thbBottomLeft_DragStarted;
				this.SW.DragDelta -= thbBottomLeft_DragDelta;
			}

			if (null != this.NE)
			{
				this.NE.DragStarted -= thbTopRight_DragStarted;
				this.NE.DragDelta -= thbTopRight_DragDelta;
			}

			if (null != this.NW)
			{
				this.NW.DragStarted -= thbTopLeft_DragStarted;
				this.NW.DragDelta -= thbTopLeft_DragDelta;
			}

		}

		#region Static Loaders

		/// <summary>
		/// Shows the Dialog and begins uploading the requested file(s)
		/// </summary>
		/// <param name="uri">The uri (absolute or relative) of the upload http handler. For OOB must be the absolute Uri.</param>
		/// <param name="files">A collection of Files to upload.</param>
		/// <param name="getMetadataCallback">A delegate that encapsulates a method that is called to retrieve the metadata string.</param>
		/// <param name="folderName">The name of the Folder the file(s) will be uploaded to. This is merely displayed in the header.</param>
		/// <param name="handlerKey">The Web Config Key that contains the Handler for the file upload request</param>
		/// <returns>The UploadFileDialog instance</returns>
		public static UploadFileDialog UploadFiles(string uri, IEnumerable<FileInfo> files, Func<UploadFileViewModel, string> getMetadataCallback, string folderName, string handlerKey)
		{
			return Show(uri, files, getMetadataCallback, folderName, handlerKey, 2, true, false);
		}
		/// <summary>
		/// Shows the Dialog but does not begin uploading the requested file(s)
		/// </summary>
		/// <param name="uri">The uri (absolute or relative) of the upload http handler. For OOB must be the absolute Uri.</param>
		/// <param name="files">A collection of Files to upload.</param>
		/// <param name="getMetadataCallback">A delegate that encapsulates a method that is called to retrieve the metadata string.</param>
		/// <param name="folderName">The name of the Folder the file(s) will be uploaded to. This is merely displayed in the header.</param>
		/// <param name="handlerKey">The Web Config Key that contains the Handler for the file upload request</param>
		/// <returns>The UploadFileDialog instance</returns>
		public static UploadFileDialog Show(string uri, IEnumerable<FileInfo> files, Func<UploadFileViewModel, string> getMetadataCallback, string folderName, string handlerKey)
		{
			return Show(uri, files, getMetadataCallback, folderName, handlerKey, 2, false, false);
		}
		/// <summary>
		/// Shows the Dialog
		/// </summary>
		/// <param name="uri">The uri (absolute or relative) of the upload http handler. For OOB must be the absolute Uri.</param>
		/// <param name="files">A collection of Files to upload.</param>
		/// <param name="getMetadataCallback">A delegate that encapsulates a method that is called to retrieve the metadata string.</param>
		/// <param name="folderName">The name of the Folder the file(s) will be uploaded to. This is merely displayed in the header.</param>
		/// <param name="handlerKey">The Web Config Key that contains the Handler for the file upload request</param>
		/// <param name="maxConcurrent">The maximum number of concurrent downloads.</param>
		/// <param name="autoUpload">Indicate if the dialog should start the upload process when it opens.</param>
		/// <param name="allowNewUpload">Indicate whether or not once the dialog is shown, if the User can initiate a new Upload.</param>
		/// <returns>The UploadFileDialog instance</returns>
		public static UploadFileDialog Show(string uri, IEnumerable<FileInfo> files, Func<UploadFileViewModel, string> getMetadataCallback, string folderName, string handlerKey, int maxConcurrent, bool autoUpload, bool allowNewUpload)
		{
			var dialog = new UploadFileDialog(getMetadataCallback, folderName, handlerKey);
			dialog.UploadUri = uri;
			dialog.MaxConcurrentUploads = maxConcurrent;
			dialog.AutoUpload = autoUpload;
			dialog.AllowNewUpload = allowNewUpload;
			dialog.Show(files);
			return dialog;
		}

		/// <summary>
		/// Shows the Dialog and begins uploading the requested file(s)
		/// </summary>
		/// <param name="files">A collection of Files to upload.</param>
		/// <param name="getMetadataCallback">A delegate that encapsulates a method that is called to retrieve the metadata string.</param>
		/// <param name="folderName">The name of the Folder the file(s) will be uploaded to. This is merely displayed in the header.</param>
		/// <param name="handlerKey">The Web Config Key that contains the Handler for the file upload request</param>
		/// <returns>The UploadFileDialog instance</returns>
		public static UploadFileDialog UploadFiles(IEnumerable<FileInfo> files, Func<UploadFileViewModel, string> getMetadataCallback, string folderName, string handlerKey)
		{
			return Show(files, getMetadataCallback, folderName, handlerKey, 2, true, false);
		}
		/// <summary>
		/// Shows the Dialog but does not begin uploading the requested file(s)
		/// </summary>
		/// <param name="files">A collection of Files to upload.</param>
		/// <param name="getMetadataCallback">A delegate that encapsulates a method that is called to retrieve the metadata string.</param>
		/// <param name="folderName">The name of the Folder the file(s) will be uploaded to. This is merely displayed in the header.</param>
		/// <param name="handlerKey">The Web Config Key that contains the Handler for the file upload request</param>
		/// <returns>The UploadFileDialog instance</returns>
		public static UploadFileDialog Show(IEnumerable<FileInfo> files, Func<UploadFileViewModel, string> getMetadataCallback, string folderName, string handlerKey)
		{
			return Show(files, getMetadataCallback, folderName, handlerKey, 2, false, false);
		}
		/// <summary>
		/// Shows the Dialog
		/// </summary>
		/// <param name="files">A collection of Files to upload.</param>
		/// <param name="getMetadataCallback">A delegate that encapsulates a method that is called to retrieve the metadata string.</param>
		/// <param name="folderName">The name of the Folder the file(s) will be uploaded to. This is merely displayed in the header.</param>
		/// <param name="handlerKey">The Web Config Key that contains the Handler for the file upload request</param>
		/// <param name="maxConcurrent">The maximum number of concurrent downloads.</param>
		/// <param name="autoUpload">Indicate if the dialog should start the upload process when it opens.</param>
		/// <param name="allowNewUpload">Indicate whether or not once the dialog is shown, if the User can initiate a new Upload.</param>
		/// <returns>The UploadFileDialog instance</returns>
		public static UploadFileDialog Show(IEnumerable<FileInfo> files, Func<UploadFileViewModel, string> getMetadataCallback, string folderName, string handlerKey, int maxConcurrent, bool autoUpload, bool allowNewUpload)
		{
			var dialog = new UploadFileDialog(getMetadataCallback, folderName, handlerKey);
			dialog.MaxConcurrentUploads = maxConcurrent;
			dialog.AutoUpload = autoUpload;
			dialog.AllowNewUpload = allowNewUpload;
			dialog.Show(files);
			return dialog;
		}

		/// <summary>
		/// Shows the Dialog and begins uploading the requested file(s)
		/// </summary>
		/// <param name="files">A collection of Files to upload.</param>
		/// <param name="getMetadataCallback">A delegate that encapsulates a method that is called to retrieve the metadata string.</param>
		/// <param name="folderName">The name of the Folder the file(s) will be uploaded to. This is merely displayed in the header.</param>
		/// <returns>The UploadFileDialog instance</returns>
		public static UploadFileDialog UploadFiles(IEnumerable<FileInfo> files, Func<UploadFileViewModel, string> getMetadataCallback, string folderName)
		{
			return Show(files, getMetadataCallback, folderName, 2, true, false);
		}
		/// <summary>
		/// Shows the Dialog but does not begin uploading the requested file(s)
		/// </summary>
		/// <param name="files">A collection of Files to upload.</param>
		/// <param name="getMetadataCallback">A delegate that encapsulates a method that is called to retrieve the metadata string.</param>
		/// <param name="folderName">The name of the Folder the file(s) will be uploaded to. This is merely displayed in the header.</param>
		/// <returns>The UploadFileDialog instance</returns>
		public static UploadFileDialog Show(IEnumerable<FileInfo> files, Func<UploadFileViewModel, string> getMetadataCallback, string folderName)
		{
			return Show(files, getMetadataCallback, folderName, 2, false, false);
		}
		/// <summary>
		/// Shows the Dialog
		/// </summary>
		/// <param name="files">A collection of Files to upload.</param>
		/// <param name="getMetadataCallback">A delegate that encapsulates a method that is called to retrieve the metadata string.</param>
		/// <param name="folderName">The name of the Folder the file(s) will be uploaded to. This is merely displayed in the header.</param>
		/// <param name="maxConcurrent">The maximum number of concurrent downloads.</param>
		/// <param name="autoUpload">Indicate if the dialog should start the upload process when it opens.</param>
		/// <param name="allowNewUpload">Indicate whether or not once the dialog is shown, if the User can initiate a new Upload.</param>
		/// <returns>The UploadFileDialog instance</returns>
		public static UploadFileDialog Show(IEnumerable<FileInfo> files, Func<UploadFileViewModel, string> getMetadataCallback, string folderName, int maxConcurrent, bool autoUpload, bool allowNewUpload)
		{
			var dialog = new UploadFileDialog(getMetadataCallback, folderName);
			dialog.MaxConcurrentUploads = maxConcurrent;
			dialog.preloadFiles = files;
			dialog.AutoUpload = autoUpload;
			dialog.AllowNewUpload = allowNewUpload;
			dialog.Show();
			return dialog;
		}

		/// <summary>
		/// Shows the Dialog and begins uploading the requested file(s)
		/// </summary>
		/// <param name="uri">The uri (absolute or relative) of the upload http handler. For OOB must be the absolute Uri.</param>
		/// <param name="files">A collection of Files to upload.</param>
		/// <param name="getMetadataCallback">A delegate that encapsulates a method that is called to retrieve the metadata string.</param>
		/// <returns>The UploadFileDialog instance</returns>
		public static UploadFileDialog UploadFiles(string uri, IEnumerable<FileInfo> files, Func<UploadFileViewModel, string> getMetadataCallback)
		{
			return Show(uri, files, getMetadataCallback, 2, true, false);
		}
		/// <summary>
		/// Shows the Dialog but does not begin uploading the requested file(s)
		/// </summary>
		/// <param name="uri">The uri (absolute or relative) of the upload http handler. For OOB must be the absolute Uri.</param>
		/// <param name="files">A collection of Files to upload.</param>
		/// <param name="getMetadataCallback">A delegate that encapsulates a method that is called to retrieve the metadata string.</param>
		/// <returns>The UploadFileDialog instance</returns>
		public static UploadFileDialog Show(string uri, IEnumerable<FileInfo> files, Func<UploadFileViewModel, string> getMetadataCallback)
		{
			return Show(uri, files, getMetadataCallback, 2, false, false);
		}
		/// <summary>
		/// Shows the Dialog
		/// </summary>
		/// <param name="uri">The uri (absolute or relative) of the upload http handler. For OOB must be the absolute Uri.</param>
		/// <param name="files">A collection of Files to upload.</param>
		/// <param name="getMetadataCallback">A delegate that encapsulates a method that is called to retrieve the metadata string.</param>
		/// <param name="maxConcurrent">The maximum number of concurrent downloads.</param>
		/// <param name="autoUpload">Indicate if the dialog should start the upload process when it opens.</param>
		/// <param name="allowNewUpload">Indicate whether or not once the dialog is shown, if the User can initiate a new Upload.</param>
		/// <returns>The UploadFileDialog instance</returns>
		public static UploadFileDialog Show(string uri, IEnumerable<FileInfo> files, Func<UploadFileViewModel, string> getMetadataCallback, int maxConcurrent, bool autoUpload, bool allowNewUpload)
		{
			var dialog = new UploadFileDialog(getMetadataCallback);
			dialog.UploadUri = uri;
			dialog.MaxConcurrentUploads = maxConcurrent;
			dialog.preloadFiles = files;
			dialog.AutoUpload = autoUpload;
			dialog.AllowNewUpload = allowNewUpload;
			dialog.Show();
			return dialog;
		}

		/// <summary>
		/// Shows the Dialog and begins uploading the requested file(s)
		/// </summary>
		/// <param name="files">A collection of Files to upload.</param>
		/// <param name="getMetadataCallback">A delegate that encapsulates a method that is called to retrieve the metadata string.</param>
		/// <returns>The UploadFileDialog instance</returns>
		public static UploadFileDialog UploadFiles(IEnumerable<FileInfo> files, Func<UploadFileViewModel, string> getMetadataCallback)
		{
			return Show(files, getMetadataCallback, 2, true, false);
		}
		/// <summary>
		/// Shows the Dialog but does not begin uploading the requested file(s)
		/// </summary>
		/// <param name="files">A collection of Files to upload.</param>
		/// <param name="getMetadataCallback">A delegate that encapsulates a method that is called to retrieve the metadata string.</param>
		/// <returns>The UploadFileDialog instance</returns>
		public static UploadFileDialog Show(IEnumerable<FileInfo> files, Func<UploadFileViewModel, string> getMetadataCallback)
		{
			return Show(files, getMetadataCallback, 2, false, false);
		}
		/// <summary>
		/// Shows the Dialog
		/// </summary>
		/// <param name="files">A collection of Files to upload.</param>
		/// <param name="getMetadataCallback">A delegate that encapsulates a method that is called to retrieve the metadata string.</param>
		/// <param name="maxConcurrent">The maximum number of concurrent downloads.</param>
		/// <param name="autoUpload">Indicate if the dialog should start the upload process when it opens.</param>
		/// <param name="allowNewUpload">Indicate whether or not once the dialog is shown, if the User can initiate a new Upload.</param>
		/// <returns>The UploadFileDialog instance</returns>
		public static UploadFileDialog Show(IEnumerable<FileInfo> files, Func<UploadFileViewModel, string> getMetadataCallback, int maxConcurrent, bool autoUpload, bool allowNewUpload)
		{
			var dialog = new UploadFileDialog(getMetadataCallback);
			dialog.MaxConcurrentUploads = maxConcurrent;
			dialog.preloadFiles = files;
			dialog.AutoUpload = autoUpload;
			dialog.AllowNewUpload = allowNewUpload;
			dialog.Show();
			return dialog;
		}

		/// <summary>
		/// Shows the Dialog and begins uploading the requested file(s)
		/// </summary>
		/// <param name="files">A collection of Files to upload.</param>
		/// <returns>The UploadFileDialog instance</returns>
		public static UploadFileDialog UploadFiles(IEnumerable<FileInfo> files)
		{
			return Show(files, 2, true, false);
		}
		/// <summary>
		/// Shows the Dialog
		/// </summary>
		/// <param name="files">A collection of Files to upload.</param>
		/// <param name="maxConcurrent">The maximum number of concurrent downloads.</param>
		/// <param name="autoUpload">Indicate if the dialog should start the upload process when it opens.</param>
		/// <param name="allowNewUpload">Indicate whether or not once the dialog is shown, if the User can initiate a new Upload.</param>
		/// <returns>The UploadFileDialog instance</returns>
		public static UploadFileDialog Show(IEnumerable<FileInfo> files, int maxConcurrent, bool autoUpload, bool allowNewUpload)
		{
			var dialog = new UploadFileDialog();
			dialog.MaxConcurrentUploads = maxConcurrent;
			dialog.preloadFiles = files;
			dialog.AutoUpload = autoUpload;
			dialog.AllowNewUpload = allowNewUpload;
			dialog.Show();
			return dialog;
		}

		/// <summary>
		/// Shows the Dialog and begins uploading the requested file(s)
		/// </summary>
		/// <param name="uri">The uri (absolute or relative) of the upload http handler. For OOB must be the absolute Uri.</param>
		/// <param name="files">A collection of Files to upload.</param>
		/// <returns>The UploadFileDialog instance</returns>
		public static UploadFileDialog UploadFiles(string uri, IEnumerable<FileInfo> files)
		{
			return Show(uri, files, 2, true, false);
		}
		/// <summary>
		/// Shows the Dialog
		/// </summary>
		/// <param name="uri">The uri (absolute or relative) of the upload http handler. For OOB must be the absolute Uri.</param>
		/// <param name="files">A collection of Files to upload.</param>
		/// <param name="maxConcurrent">The maximum number of concurrent downloads.</param>
		/// <param name="autoUpload">Indicate if the dialog should start the upload process when it opens.</param>
		/// <param name="allowNewUpload">Indicate whether or not once the dialog is shown, if the User can initiate a new Upload.</param>
		/// <returns>The UploadFileDialog instance</returns>
		public static UploadFileDialog Show(string uri, IEnumerable<FileInfo> files, int maxConcurrent, bool autoUpload, bool allowNewUpload)
		{
			var dialog = new UploadFileDialog();
			dialog.UploadUri = uri;
			dialog.MaxConcurrentUploads = maxConcurrent;
			dialog.preloadFiles = files;
			dialog.AutoUpload = autoUpload;
			dialog.AllowNewUpload = allowNewUpload;
			dialog.Show();
			return dialog;
		}

		#endregion

		#region Actions
		bool CanBrowse()
		{
			return this.btnBrowse.IsEnabled;
		}
		void Browse()
		{
			try
			{
				OpenFileDialog dialog = new OpenFileDialog();
				dialog.Multiselect = this.AllowMultiselect;
				#region Apply the FileFilter
				try
				{
					if (!string.IsNullOrEmpty(this.Filters))
						dialog.Filter = this.Filters;
					else
						dialog.Filter = "All Files (*.*)|*.*";
				}
				catch
				{
					dialog.Filter = "All Files (*.*)|*.*";
				}
				#endregion

				if (dialog.ShowDialog() == true)
				{
					this.AddFiles(dialog.Files);
					if (this.AutoUpload && viewModel.Files.Count > 0)
						this.BeginUpload();
				}
			}
			catch (Exception ex)
			{
				MessageBox.Show(ex.Message);
			}
		}
		bool CanUpload()
		{
			return this.btnUpload.IsEnabled;
		}
		void Upload()
		{
			if (viewModel.Files.Count == 0)
			{
				MessageBox.Show("No files to upload. Please select one or more files first.", "Upload Files", MessageBoxButton.OK);
				return;
			}
			if (string.IsNullOrEmpty(this._uploadUri))
			{
				this._uploadUri = UploadFileDialog.DefaultUploadUri;
				Uri uri = null;
				try { uri = FileQuery.ResolveUrl(this._uploadUri); }
				catch (Exception ex)
				{
					MessageBox.Show(ex.Message, "Upload", MessageBoxButton.OK);
					return;
				}
				this.viewModel.UploadUri = uri;
			}
			this.viewModel.BeginUpload(this.MaxConcurrentUploads);
		}
		void BeginUpload()
		{
			System.Threading.ThreadPool.QueueUserWorkItem((o) =>
			{
				// Allow time for the UI to update.
				System.Threading.Thread.Sleep(100);
				this.Dispatcher.BeginInvoke(() =>
				{
					this.Upload();
				});
			});
		}
		bool CanReset()
		{
			return this.btnClearAll.IsEnabled;
		}
		void Reset()
		{
			this.lblResultsMessage.Foreground = new SolidColorBrush(Colors.Black);
			this.lblResultsMessage.Text = "Ready";
			this.viewModel.Reset();
		}
		void Cancel()
		{
			this.viewModel.CancelUpload();
		}

		bool AddFiles(IEnumerable<FileInfo> files)
		{
			if (files.Count() > 1 && !this.AllowMultiselect)
				MessageBox.Show("You are not allowed to select multiple files, only one file will be uploaded.", this.Title.ToString(), MessageBoxButton.OK);
			foreach (var file in files)
			{
				if (this.AddFile(file) && !this.AllowMultiselect)
					break;
			}
			return viewModel.ReadyCount > 0;
		}
		bool AddFile(FileInfo file)
		{
			string fileName = file.Name;

			try
			{
				#region Validation

				// Skip Folders or Directories
				if (string.IsNullOrEmpty(file.Extension) && !file.Exists)
				{
					this.ShowFolderNotAllowed(fileName);
					UploadFileViewModel.CreateAsFailed(this.viewModel, file, "Folders not allowed.");
					return false;
				}

				// Skip excluded file extensions
				excludedExt = new List<string>(this.ExcludeFileExtension.Split('|'));
				if (excludedExt.Contains(file.Extension))
				{
					this.ShowFileRestricted(fileName);
					UploadFileViewModel.CreateAsFailed(this.viewModel, file, "Requested file not allowed.");
					return false;
				}

				// Skip files already added to the list
				if (viewModel.ContainsFile(fileName))
				{
					this.ShowFileAlreadySelected(fileName);
					UploadFileViewModel.CreateAsFailed(this.viewModel, file, "File already selected.");
					return false;
				}

				//Check for the file size limit (configurable)
				if (file.Length > this.MaxFileSizeKB)
				{
					this.ShowFileTooLarge(fileName);
					UploadFileViewModel.CreateAsFailed(this.viewModel, file, "File is too large.");
					return false;
				}

				//Check for the file size limit (too small)
				if (file.Length == 0)
				{
					this.ShowFileTooSmall(fileName);
					UploadFileViewModel.CreateAsFailed(this.viewModel, file, "File is empty.");
					return false;
				}

				#endregion

				UploadFileViewModel fileViewModel = new UploadFileViewModel();
				fileViewModel.Initialize(this.viewModel, file);
				this.viewModel.Files.Add(fileViewModel);
				return true;

			}
			catch
			{
				MessageBox.Show(string.Format(Strings.UnableToAddFile_Message, fileName), Strings.UnableToAddFile_Title, MessageBoxButton.OK);
			}

			return false;
		}
		#endregion

		#region VisualState
		bool GotoState(string state)
		{
			return this.GotoState(state, true);
		}
		bool GotoState(string state, bool useTransition)
		{
			return VisualStateManager.GoToState(this, state, useTransition);
		}
		void GotoFocused()
		{
			if (null != closeButton)
				closeButton.SetFocusedAppearance();
			this.SetBackground(true);
		}
		void GotoUnfocused()
		{
			if (null != closeButton)
				closeButton.SetUnfocusedAppearance();
			this.SetBackground(false);
		}
		void SetBackground(bool isFocused)
		{
			if (null != mainBackground && null != this.Background)
			{
				RadialGradientBrush bk = mainBackground.Background as RadialGradientBrush;
				if (isFocused)
					bk.GradientStops[0].Color = Color.FromArgb(bkfocusAlpha, this.Background.Color.R, this.Background.Color.G, this.Background.Color.B);
				else
					bk.GradientStops[0].Color = Color.FromArgb(bkunfocusAlpha, this.Background.Color.R, this.Background.Color.G, this.Background.Color.B);
			}
		}
		#endregion

		#region MessageBox Helpers
		internal void ShowCompleted()
		{
			if (!this.Dispatcher.CheckAccess())
			{
				this.Dispatcher.BeginInvoke(() =>
					{
						this.ShowCompleted();
					});
				return;
			}

			this.UploadedCount = this.viewModel.FinishedCount;
			bool success = this.UploadedCount == viewModel.Files.Count;
			bool warning = viewModel.CanceledCount > 0;
			if (warning)
				if (viewModel.CanceledCount + viewModel.FinishedCount == viewModel.Files.Count)
					success = true;

			if (success)
				this.lblResultsMessage.Foreground = new SolidColorBrush(Colors.Green);
			else if (warning)
				this.lblResultsMessage.Foreground = new SolidColorBrush(Colors.Blue);
			else
				this.lblResultsMessage.Foreground = new SolidColorBrush(Colors.Brown);

			#region Notify User
			this.lblResultsMessage.Text = viewModel.StatusFormatted;
			bool canceled = this.viewModel.State == UploadState.Canceled;
			if (canceled)
			{
				if (this.closeOnCancel)
				{
					this.DialogResult = false;
				}
				else
				{
					string title = "Upload Canceled";
					string message = "Upload was canceled ...";
					ShowMessage(title, message);
				}
			}
			else
			{
				string title = "Upload Completed";
				if (!success)
				{
					#region Show Error
					string message = "Upload completed with errors or some files were canceled.";
					ShowMessage(title, message);
					#endregion
				}
				else if (warning)
				{
					#region Show Warning
					string message = "Upload completed, but some files were canceled";
					ShowMessage(title, message);
					#endregion
				}
				else
				{
					// Success
					if (this.AutoClose)
					{
						this.OnCompleted(this.UploadedCount);
						this.DialogResult = true;
						return;
					}
				}
			}
			#endregion

			this.OnCompleted(this.UploadedCount);
		}
		void ShowFileAlreadySelected(string fileName)
		{
			string msg = "File [ {0} ] has already been selected.";
			msg = string.Format(msg, fileName);
			MessageBox.Show(msg, this.Title.ToString(), MessageBoxButton.OK);
		}
		void ShowFileRestricted(string fileName)
		{
			string msg = "File [ {0} ] is not allowed.";
			msg = string.Format(msg, fileName);
			MessageBox.Show(msg, this.Title.ToString(), MessageBoxButton.OK);
		}
		void ShowFileTooLarge(string fileName)
		{
			string msg = "File [ {0} ] is too large (max size allowed: {1} KB); this file will be skipped.";
			msg = string.Format(msg, fileName, (this.MaxFileSizeKB));
			MessageBox.Show(msg, this.Title.ToString(), MessageBoxButton.OK);
		}
		void ShowFileTooSmall(string fileName)
		{
			string msg = "File [ {0} ] is a zero length file; this file will be skipped.";
			msg = string.Format(msg, fileName);
			MessageBox.Show(msg, this.Title.ToString(), MessageBoxButton.OK);
		}
		void ShowFolderNotAllowed(string fileName)
		{
			string msg = "File [ {0} ] appears to be a Folder or a Directory. this file will be skipped.";
			msg = string.Format(msg, fileName);
			MessageBox.Show(msg, this.Title.ToString(), MessageBoxButton.OK);
		}
		void ShowMessage(string title, string message)
		{
			this.Dispatcher.BeginInvoke(() =>
				{
					MessageBox.Show(message, title, MessageBoxButton.OK);
				});
		}
		#endregion

		#region Resize Helpers

		void LeftStart()
		{
			this.newWidth = this.contentGrid.Width;
			this.newLeft = Convert.ToSingle(this.contentRoot.GetValue(Canvas.LeftProperty));
			this.originalLeft = newLeft + (this.newWidth - this.minWidth);
		}
		void LeftResize(double change)
		{
			this.newLeft += change;
			this.newWidth += (-1 * change);
			if (this.newLeft > this.originalLeft)
			{
				this.contentRoot.SetValue(Canvas.LeftProperty, this.originalLeft);
				this.contentGrid.Width = this.minWidth;
			}
			else
			{
				this.contentRoot.SetValue(Canvas.LeftProperty, this.newLeft);
				this.contentGrid.Width = this.newWidth;
			}
		}

		void TopStart()
		{
			this.newHeight = this.contentGrid.Height;
			this.newTop = Convert.ToSingle(this.contentRoot.GetValue(Canvas.TopProperty));
			this.originalTop = newTop + (this.newHeight - this.minHeight);
		}
		void TopResize(double change)
		{
			this.newHeight += (-1 * change);
			this.newTop += change;
			if (this.newTop > this.originalTop)
			{
				this.contentRoot.SetValue(Canvas.TopProperty, this.originalTop);
				this.contentGrid.Height = this.minHeight;
			}
			else
			{
				this.contentRoot.SetValue(Canvas.TopProperty, this.newTop);
				this.contentGrid.Height = this.newHeight;
			}
		}

		void RightStart()
		{
			this.newWidth = this.contentGrid.Width;
		}
		void RightResize(double change)
		{
			this.newWidth += change;
			if (this.newWidth < this.minWidth)
				this.contentGrid.Width = this.minWidth;
			else
				this.contentGrid.Width = this.newWidth;
		}

		void BottomStart()
		{
			this.newHeight = this.contentGrid.Height;
		}
		void BottomResize(double change)
		{
			this.newHeight += change;
			if (this.newHeight < this.minHeight)
				this.contentGrid.Height = this.minHeight;
			else
				this.contentGrid.Height = this.newHeight;
		}


		#endregion

		#endregion

		#region Event Handlers
		void Root_MouseRightButtonDown(object sender, MouseButtonEventArgs e)
		{
			e.Handled = true;
		}
		void FileList_Drop(object sender, DragEventArgs e)
		{
			if (null == e.Data)
				return;
			if (e.Data.GetDataPresent(DataFormats.FileDrop))
			{
				int current = this.FileList.Items.Count;
				if (!this.AllowMultiselect && current > 0)
					return;
				FileInfo[] droppedFiles = e.Data.GetData(DataFormats.FileDrop) as FileInfo[];
				if (null != droppedFiles && droppedFiles.Length > 0)
					this.AddFiles(droppedFiles);
				if (current == 0 && this.FileList.Items.Count > 0)
					this.FileList.SelectedIndex = 0;
				if (this.AutoUpload && this.FileList.Items.Count > 0)
					this.Upload();
			}
		}
		void FileList_GotFocus(object sender, RoutedEventArgs e)
		{
			ListBoxItem listBoxItem = e.OriginalSource as ListBoxItem;
			if (null != listBoxItem && !listBoxItem.IsSelected)
				listBoxItem.IsSelected = true;
		}
		void FileList_MouseRightButtonDown(object sender, MouseButtonEventArgs e)
		{
			e.Handled = true;
		}
		void FileList_MouseRightButtonUp(object sender, MouseButtonEventArgs e)
		{
			ObservableCollection<ContextMenuItem> menuItems = new ObservableCollection<ContextMenuItem>();

			if (this.btnBrowse.Visibility == System.Windows.Visibility.Visible)
				menuItems.Add(new TextContextMenuItem() { Text = this.btnBrowse.Content.ToString(), Execute = Browse, CanExecute = CanBrowse, Image = ResourceMgr.FolderImage });

			if (this.btnUpload.Visibility == System.Windows.Visibility.Visible)
				menuItems.Add(new TextContextMenuItem() { Text = this.btnUpload.Content.ToString(), Execute = Upload, CanExecute = CanUpload, Image = ResourceMgr.UploadFileImage });

			if (this.btnClearAll.Visibility == System.Windows.Visibility.Visible)
				menuItems.Add(new TextContextMenuItem() { Text = this.btnClearAll.Content.ToString(), Execute = Reset, CanExecute = CanReset });

			if (menuItems.Count > 0)
			{
				ContextMenu ctx = new ContextMenu(this);
				foreach (var item in menuItems)
					ctx.MenuItems.Add(item);
				ctx.Show(e.GetPosition(null));
			}
		}
		void btnBrowse_Click(object sender, RoutedEventArgs e)
		{
			this.btnBrowse.IsEnabled = false;
			this.Browse();
			this.btnBrowse.IsEnabled = true;
		}
		void btnUpload_Click(object sender, RoutedEventArgs e)
		{
			this.Upload();
		}
		void btnClearAll_Click(object sender, RoutedEventArgs e)
		{
			this.Reset();
		}
		void btnCancel_Click(object sender, RoutedEventArgs e)
		{
			if (viewModel.State == UploadState.Canceled)
			{
				this.DialogResult = false;
			}
			else if (viewModel.State == UploadState.InProgress)
			{
				this.Cancel();
			}
			else
			{
				this.DialogResult = true;
			}
		}
		void txtFilesCount_MouseRightButtonUp(object sender, MouseButtonEventArgs e)
		{
			if (this.viewModel.IsDebugWriting)
			{
				UploadFileViewModel file = this.FileList.SelectedItem as UploadFileViewModel;
				if (null == file)
					return;
				var d = new DebugMessageDialog();
				d.Message = file.DebugMessage;
				d.Closed += (s, args) => { this.Focus(); };
				d.Show();
			}
		}
		void contentGrid_SizeChanged(object s, SizeChangedEventArgs e)
		{
			if (e.PreviousSize.Width == 0)
				return;
			double delta = e.NewSize.Width - e.PreviousSize.Width;
			this.viewModel.WidthChanged(delta);
			if (null != this.txbFileHeader)
			{
				double newWidth = this.txbFileHeader.Width + delta;
				if (newWidth > defaultFileNameWidth)
					this.txbFileHeader.Width = newWidth;
				else
					this.txbFileHeader.Width = defaultFileNameWidth;
			}
		}
		void UploadFileDialog_GotFocus(object sender, RoutedEventArgs e)
		{
			this.GotoFocused();
		}
		void UploadFileDialog_LostFocus(object sender, RoutedEventArgs e)
		{
			this.GotoUnfocused();
		}
		void viewModel_PropertyChanged(object sender, PropertyChangedEventArgs e)
		{
			if (e.PropertyName == "State")
			{
				if (this.DialogResult.HasValue)
					return;
				this.GotoState(this.viewModel.State.ToString());
				switch (this.viewModel.State)
				{
					case UploadState.InProgress:
						this.lblResultsMessage.Text = "Uploading...";
						if (null != this.FileList)
							this.FileList.AllowDrop = false;
						break;

					case UploadState.NotStarted:
						if (null != this.FileList)
							this.FileList.AllowDrop = true;
						break;
				}
			}
		}
		void thbRight_DragStarted(object s, DragStartedEventArgs e)
		{
			this.RightStart();
		}
		void thbRight_DragDelta(object s, DragDeltaEventArgs e)
		{
			this.RightResize(e.HorizontalChange);
		}
		void thbBottom_DragStarted(object s, DragStartedEventArgs e)
		{
			this.BottomStart();
		}
		void thbBottom_DragDelta(object s, DragDeltaEventArgs e)
		{
			this.BottomResize(e.VerticalChange);
		}
		void thbLeft_DragStarted(object s, DragStartedEventArgs e)
		{
			this.LeftStart();
		}
		void thbLeft_DragDelta(object s, DragDeltaEventArgs e)
		{
			this.LeftResize(e.HorizontalChange);
		}
		void thbTop_DragStarted(object s, DragStartedEventArgs e)
		{
			this.TopStart();
		}
		void thbTop_DragDelta(object s, DragDeltaEventArgs e)
		{
			this.TopResize(e.VerticalChange);
		}
		void thbBottomRight_DragStarted(object s, DragStartedEventArgs e)
		{
			this.BottomStart();
			this.RightStart();
		}
		void thbBottomRight_DragDelta(object s, DragDeltaEventArgs e)
		{
			this.BottomResize(e.VerticalChange);
			this.RightResize(e.HorizontalChange);
		}
		void thbBottomLeft_DragStarted(object s, DragStartedEventArgs e)
		{
			this.BottomStart();
			this.LeftStart();
		}
		void thbBottomLeft_DragDelta(object s, DragDeltaEventArgs e)
		{
			this.BottomResize(e.VerticalChange);
			this.LeftResize(e.HorizontalChange);
		}
		void thbTopRight_DragStarted(object s, DragStartedEventArgs e)
		{
			this.TopStart();
			this.RightStart();
		}
		void thbTopRight_DragDelta(object s, DragDeltaEventArgs e)
		{
			this.TopResize(e.VerticalChange);
			this.RightResize(e.HorizontalChange);
		}
		void thbTopLeft_DragStarted(object s, DragStartedEventArgs e)
		{
			this.TopStart();
			this.LeftStart();
		}
		void thbTopLeft_DragDelta(object s, DragDeltaEventArgs e)
		{
			this.TopResize(e.VerticalChange);
			this.LeftResize(e.HorizontalChange);
		}
		#endregion
	}
	#endregion
}